/**************************************************************************** 
 * Meta，一个轻量级的静态分区Hypervisor 
 *
 * 版权(c) , 2022-
 *
 * 作者:
 *      Tupelo Shen <shenwanjiang2013@163.com>
 *
 * Meta是一个自由软件，你可以在遵循GNU GPLv2协议的条件下，重新修改并发布它。
 *
 ***************************************************************************/

#include <ipc.h>

#include <cpu.h>
#include <vmm.h>
#include <hypercall.h>

enum {IPC_NOTIFY};

union ipc_msg_data {
    struct {
        uint8_t shmem_id;
        uint8_t event_id;
    };
    uint64_t raw;
};

static size_t shmem_table_size;
static struct shmem *shmem_table;

/****************************************************************************
 ****                       私有函数接口                                 ****
 ****************************************************************************/
static void ipc_alloc_shmem(void);
static void ipc_setup_masters(const struct vm_config* vm_config, bool vm_master);
static void ipc_handler(uint32_t event, uint64_t data);
static void ipc_notify(size_t shmem_id, size_t event_id);
static struct ipc* ipc_find_by_shmemid(struct vm* vm, size_t shmem_id);

/****************************************************************************
 ****                       共有函数定义                                 ****
 ****************************************************************************/
/**
 * 功能描述: IPC初始化（VM之间通信）
 * 入口参数: vm_config,     VM配置
 *           vm_master,     VM主核
 * 出口参数: void
 * 返 回 值: void
 * 修改日期: 2022-09-21
 */
void ipc_init(const struct vm_config* vm_config, bool vm_master) {

    shmem_table_size = vm_config_ptr->shmemlist_size;
    shmem_table = vm_config_ptr->shmemlist;
    
    /* 分配共享物理内存 */
    if(cpu.id == CPU_MASTER) {
        ipc_alloc_shmem();
    }

    /* 设置主核 */
    ipc_setup_masters(vm_config, vm_master);
}

/**
 * 功能描述: 根据共享内存ID，获取ipc使用的共享内存
 * 入口参数: shmem_id,      共享内存
 * 出口参数: void
 * 返 回 值: shmem_t,       指向该共享内存数据结构的指针
 * 修改日期: 2022-09-21
 */
struct shmem* ipc_get_shmem(size_t shmem_id)
{
    if(shmem_id < shmem_table_size) {
        return &shmem_table[shmem_id];
    } else {
        return NULL;
    }
}

/**
 * 功能描述: 通过HVC指令，建立IPC通信
 * 入口参数: arg0,  ipc id
 *           arg1,  ipc event
 *           arg2,    
 * 出口参数: void
 * 返 回 值: int64_t,  成功返回0，否则返回负值
 * 修改日期: 2022-09-21
 */
/* 注册IPC处理函数，以及句柄 */
CPU_MSG_HANDLER(ipc_handler, IPC_CPUSMG_ID);

unsigned long ipc_hypercall(unsigned long ipc_id,
                            unsigned long ipc_event,
                            unsigned long arg2)
{
    unsigned long ret = -HC_E_SUCCESS;

    struct shmem *shmem = NULL; 
    bool valid_ipc_obj = ipc_id < cpu.vcpu->vm->ipc_num;
    if(valid_ipc_obj) {
        shmem = ipc_get_shmem(cpu.vcpu->vm->ipcs[ipc_id].shmem_id);
    }
    bool valid_shmem = shmem != NULL;

    if(valid_ipc_obj && valid_shmem) {

        cpumap_t ipc_cpu_masters = shmem->cpu_masters & ~cpu.vcpu->vm->cpus;

        union ipc_msg_data data = {
            .shmem_id = cpu.vcpu->vm->ipcs[ipc_id].shmem_id,
            .event_id = ipc_event,
        };
        struct cpu_msg msg = {IPC_CPUSMG_ID, IPC_NOTIFY, data.raw};

        for (size_t i = 0; i < platform.cpu_num; i++) {
            if (ipc_cpu_masters & (1ULL << i)) {
                cpu_send_msg(i, &msg);
            }
        }

    } else {
        ret = -HC_E_INVAL_ARGS;
    }

    return ret;
}

/****************************************************************************
 ****                       私有函数定义                                 ****
 ****************************************************************************/
/**
 * 功能描述: 为IPC分配物理内存
 * 入口参数: vm_config,     VM配置
 *           vm_master,     主VM
 * 出口参数: void
 * 返 回 值: void
 * 修改日期: 2022-09-21
 */
static void ipc_alloc_shmem(void)
{
    for (size_t i = 0; i < shmem_table_size; i++) {
        struct shmem *shmem = &shmem_table[i];
        if(!shmem->place_phys) {
            size_t n_pg = NUM_PAGES(shmem->size);
            struct ppages ppages = mem_alloc_ppages(shmem->colors, n_pg, false);
            if(ppages.size < n_pg) {
                ERROR("failed to allocate shared memory");
            }
            shmem->phys = ppages.base;
        }
    }
}

/**
 * 功能描述: 为IPC设置主核（主核负责发送IPC消息）
 * 入口参数: vm_config,     VM配置
 *           vm_master,     VM主核
 * 出口参数: void
 * 返 回 值: void
 * 修改日期: 2022-09-21
 */
static void ipc_setup_masters(const struct vm_config* vm_config, bool vm_master)
{
    static spinlock_t lock = SPINLOCK_INITVAL;

    for(size_t i = 0; i < vm_config_ptr->shmemlist_size; i++) {
        vm_config_ptr->shmemlist[i].cpu_masters = 0;
    }

    cpu_sync_barrier(&cpu_glb_sync);

    if(vm_master) {
        for(size_t i = 0; i < vm_config->platform.ipc_num; i++) {
            spin_lock(&lock);
            struct shmem *shmem = ipc_get_shmem(vm_config->platform.ipcs[i].shmem_id);
            if(shmem != NULL) {
                shmem->cpu_masters |= (1ULL << cpu.id);
            }
            spin_unlock(&lock);
        }
    }
}

/**
 * 功能描述: IPC处理函数
 * 入口参数: vm_config,     VM配置
 *           vm_master,     VM主核
 * 出口参数: void
 * 返 回 值: void
 * 修改日期: 2022-09-21
 */
static void ipc_handler(uint32_t event, uint64_t data)
{
    union ipc_msg_data ipc_data = { .raw = data };
    switch(event){
        case IPC_NOTIFY: 
            ipc_notify(ipc_data.shmem_id, ipc_data.event_id);
        break;
    }
}

/**
 * 功能描述: 将IPC通知VM
 * 入口参数: shmem_id,  共享内存的ID
 *           event_id,  事件ID
 * 出口参数: void
 * 返 回 值: void
 * 修改日期: 2022-09-21
 */
static void ipc_notify(size_t shmem_id, size_t event_id)
{
    struct ipc* ipc_obj = ipc_find_by_shmemid(cpu.vcpu->vm, shmem_id);
    if(ipc_obj != NULL && event_id < ipc_obj->interrupt_num) {
        irqid_t irq_id = ipc_obj->interrupts[event_id];
        interrupts_vm_inject(cpu.vcpu->vm, irq_id);
    }
}

/**
 * 功能描述: 根据共享内存ID获取ipc对象
 * 入口参数: vm,        VM数据结构
 *           shmem_id,  共享内存ID
 * 出口参数: void
 * 返 回 值: struct ipc,     返回ipc对象指针
 * 修改日期: 2022-09-21
 */
static struct ipc* ipc_find_by_shmemid(struct vm* vm, size_t shmem_id)
{
    struct ipc* ipc_obj = NULL;

    for(size_t i = 0; i < vm->ipc_num; i++) {
        if(vm->ipcs[i].shmem_id == shmem_id) {
            ipc_obj = &vm->ipcs[i];
            break;
        }
    }

    return ipc_obj;
}
